package com.coffee.mybatis;

import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.coffee.core.CoffeeEnum;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Objects;

/**
 * @author songkui
 * @since 2025/3/26
 */


public class CoffeeEnumTypeHandler<E extends CoffeeEnum<E>> extends BaseTypeHandler<E> {

    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private final Class<E> enumClassType;
    private final Class<?> propertyType;
    private final Invoker getInvoker;

    public CoffeeEnumTypeHandler(Class<E> enumClassType) {
        if (enumClassType == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        } else {
            this.enumClassType = enumClassType;
            MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
            String name = "code";
            this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
            this.getInvoker = metaClass.getGetInvoker(name);
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, parameter.code());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object value = rs.getObject(columnName);
        return (E)(null != value && !rs.wasNull() ? this.valueOf(value) : null);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Object value = rs.getObject(columnIndex, this.propertyType);
        return (E)(null != value && !rs.wasNull() ? this.valueOf(value) : null);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object value = cs.getObject(columnIndex, this.propertyType);
        return (E)(null != value && !cs.wasNull() ? this.valueOf(value) : null);
    }

    private E valueOf(Object value) {
        E[] es = (E[])(this.enumClassType.getEnumConstants());
        return (E)(Arrays.stream(es).filter((e) -> this.equalsValue(value, this.getValue(e))).findAny().orElse(null));
    }

    protected boolean equalsValue(Object sourceValue, Object targetValue) {
        String sValue = StringUtils.toStringTrim(sourceValue);
        String tValue = StringUtils.toStringTrim(targetValue);
        return sourceValue instanceof Number && targetValue instanceof Number && (new BigDecimal(sValue)).compareTo(new BigDecimal(tValue)) == 0 ? true : Objects.equals(sValue, tValue);
    }

    private Object getValue(Object object) {
        try {
            return this.getInvoker.invoke(object, new Object[0]);
        } catch (ReflectiveOperationException e) {
            throw ExceptionUtils.mpe(e);
        }
    }

}
